/**
 * 
 */
package fun.diaoer.common;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import fun.diaoer.exception.GenericException;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;

@Slf4j
public class DiaoerFileUtils extends FileUtils {

	private static final String EXPIRES = "Expires";
	private static final String PRAGMA = "Pragma";
	private static final String CONTENT_DISPOSITION = "Content-Disposition";
	private static final String CACHE_CONTROL = "Cache-Control";

	public static byte[] byteMerger(byte[] bt1, byte[] bt2) {
		byte[] bt3 = new byte[bt1.length + bt2.length];
		System.arraycopy(bt1, 0, bt3, 0, bt1.length);
		System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
		return bt3;
	}

	private static ByteArrayOutputStream cloneInputStream(InputStream is) {
		ByteArrayOutputStream os = null;
		try {
			os = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int len;
			while ((len = is.read(buffer)) > -1) {
				os.write(buffer, 0, len);
			}
			os.flush();

		} catch (IOException e) {
			log.error(e.getMessage(), e);
		} finally {
			IOUtils.closeQuietly(is);
		}
		return os;
	}

	/**
	 * 解析base64，存到指定的位置，返回图片所在路径
	 * 
	 * @param base64Info
	 * @param uploadFileType
	 * @return
	 * @throws IOException
	 */
	public static String decodeBase64(String base64Info, String path, String fileNamePrefix, String uploadFileType)
			throws IOException {
		if (StringUtils.isEmpty(base64Info)) {
			return null;
		}

		// 获取绝对路径
		File filePath = new File(path);
		String imgPath = StringUtil.nullToEmpty(fileNamePrefix) + Contants.getGuid() + '.' + uploadFileType;
		// 设置图片名称及格式
		String picPath = filePath + File.separator + imgPath;
		if (!filePath.exists()) {
			filePath.mkdirs();
		}
		try (OutputStream os = new FileOutputStream(picPath)) {
			byte[] buffer = Base64.decodeBase64(base64Info.getBytes(StandardCharsets.UTF_8));
			os.write(buffer);
			os.flush();
		} catch (IOException e) {
			throw new GenericException(e);
		}
		return imgPath;
	}

	public static ResponseEntity<byte[]> downloadFile(byte[] bt, String fileName) {
		if (bt == null) {
			log.error("下载文件出错,文件不存在");
		}
		HttpHeaders headers = getHttpHeaders(fileName);
		headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
		return new ResponseEntity<>(bt, headers, HttpStatus.OK);// 开始下载
	}

	public static ResponseEntity<byte[]> downloadFile(InputStream inStream, String fileName) {
		return downloadFile(input2byte(inStream), fileName);
	}

	public static ResponseEntity<byte[]> downloadMobileFile(byte[] bt, String fileName) {
		if (bt == null) {
			log.error("下载文件出错,文件不存在");
		}
		HttpHeaders headers = getHttpHeaders(fileName);
		headers.setContentType(MediaType.APPLICATION_PDF);
		return new ResponseEntity<>(bt, headers, HttpStatus.OK);// 开始下载
	}

	/**
	 * 文件名转码
	 * 
	 * @return
	 */
	public static String encodeFileName(String fileName) {
		try {
			// encode编码UTF-8 解决大多数中文乱码
			fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
		} catch (UnsupportedEncodingException e) {
			log.error("文件名转码失败", e);
		}
		fileName = fileName.replace("+", "%20");// encode后替换空格 解决空格问题
		return fileName;
	}

	private static String getExtensionNme(String fileName) {
		return StringUtils.isNotEmpty(fileName) ? fileName.substring(fileName.lastIndexOf('.') + 1) : null;
	}

	private static String getFileNme(String fileName) {
		return StringUtils.isNotEmpty(fileName) ? fileName.substring(0, fileName.lastIndexOf('.')) : null;
	}

	/**
	 * @param fileName
	 * @return
	 */
	private static HttpHeaders getHttpHeaders(String fileName) {
		HttpHeaders headers = new HttpHeaders();// 设置一个head
		headers.add(CACHE_CONTROL, "no-cache, no-store, must-revalidate");
		headers.add(CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(fileName));
		headers.add(PRAGMA, "no-cache");
		headers.add(EXPIRES, "0");
		return headers;
	}

	/**
	 * 下载文件
	 * 
	 * @return
	 */
	public static ResponseEntity<FileSystemResource> getResponseResource(File file) {
		if (file == null || !file.exists()) {
			return null;
		}
		HttpHeaders headers = getHttpHeaders(file.getName());
		headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
		return ResponseEntity.ok().headers(headers).contentLength(file.length())
				.contentType(MediaType.parseMediaType("application/octet-stream")).body(new FileSystemResource(file));
	}

	private static final byte[] input2byte(InputStream inStream) {
		byte[] in2b = null;
		try (ByteArrayOutputStream swapStream = new ByteArrayOutputStream()) {
			byte[] buff = new byte[100];
			int rc = 0;
			while ((rc = inStream.read(buff, 0, 100)) > 0) {
				swapStream.write(buff, 0, rc);
			}
			in2b = swapStream.toByteArray();

		} catch (IOException e) {
			log.error("读取文件失败", e);
		}
		return in2b;
	}

	public static List<String> readTxtToStrList(InputStream is) {
		try (ByteArrayOutputStream os = cloneInputStream(is)) {
			InputStream is2 = new ByteArrayInputStream(os.toByteArray());
			return readTxtToStrList(new ByteArrayInputStream(os.toByteArray()), resolveCode(is2));
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		return Collections.emptyList();

	}

	public static List<String> readTxtToStrList(InputStream is, String charset) {
		// 使用ArrayList来存储每行读取到的字符串
		if (is == null) {
			return Collections.emptyList();
		}
		List<String> segments = null;
		try (InputStreamReader inputReader = new InputStreamReader(is, charset);
				BufferedReader bf = new BufferedReader(inputReader);) {
			segments = new ArrayList<>();
			// 按行读取字符串
			String str;
			while ((str = bf.readLine()) != null) {
				if (StringUtils.isNotBlank(str)) {
					segments.add(str);
				}
			}
		} catch (IOException e) {
			log.error(e.getMessage(), e);
		} finally {
			IOUtils.closeQuietly(is);
		}
		return segments;
	}

	public static String resolveCode(InputStream is) {
		String charset = "GBK";
		byte[] first3Bytes = new byte[3];
		try {
			boolean checked = false;
			is.mark(0);
			int read = is.read(first3Bytes, 0, 3);
			if (read == -1) {
				return charset; // 文件编码为 ANSI
			} else if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) {
				charset =StandardCharsets.UTF_16LE.name() ; // 文件编码为 Unicode
				checked = true;
			} else if (first3Bytes[0] == (byte) 0xFE && first3Bytes[1] == (byte) 0xFF) {
				charset = StandardCharsets.UTF_16BE.name(); // 文件编码为 Unicode big endian
				checked = true;
			} else if (first3Bytes[0] == (byte) 0xEF && first3Bytes[1] == (byte) 0xBB
					&& first3Bytes[2] == (byte) 0xBF) {
				charset = StandardCharsets.UTF_8.name(); // 文件编码为 UTF-8
				checked = true;
			}
			is.reset();
			if (!checked) {
				while ((read = is.read()) != -1) {
					if (read >= 0xF0) {
						break;
					}
					if (0x80 <= read && read <= 0xBF) { // 单独出现BF以下的，也算是GBK
						break;
					}
					if (0xC0 <= read && read <= 0xDF) {
						read = is.read();
						if (0x80 <= read && read <= 0xBF) // 双字节 (0xC0 - 0xDF)
							// (0x80
							// - 0xBF),也可能在GB编码内
							continue;
						else {
							break;
						}
					} else if (0xE0 <= read && read <= 0xEF) {// 也有可能出错，但是几率较小
						read = is.read();
						if (0x80 <= read && read <= 0xBF) {
							read = is.read();
							if (0x80 <= read && read <= 0xBF) {
								charset = StandardCharsets.UTF_8.name();
								break;
							} else {
								break;
							}
						} else {
							break;
						}
					}
				}
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		} finally {
			IOUtils.closeQuietly(is);
		}
		return charset;
	}

	public static String uploadFile(String path, MultipartFile multipartFile) throws IOException {
		return uploadFile(path, multipartFile, null);
	}

	public static String uploadFile(String path, MultipartFile multipartFile, String fileNamePrefix)
			throws IOException {
		return uploadFile(path, multipartFile, fileNamePrefix, Boolean.FALSE);
	}

	public static String uploadFile(String path, MultipartFile multipartFile, String fileNamePrefix,
			boolean isAddOriginalFileNme) throws IOException {
		String newFileName = null;
		if (!multipartFile.isEmpty()) {
			String fileName = multipartFile.getOriginalFilename();
			String fileType = getExtensionNme(fileName);
			if (isAddOriginalFileNme) {
				newFileName = StringUtil.nullToEmpty(fileNamePrefix) + Contants.getGuid() + "-" + getFileNme(fileName)
						+ '.' + fileType;
			} else {
				newFileName = StringUtil.nullToEmpty(fileNamePrefix) + Contants.getGuid() + '.' + fileType;
			}
			FileUtils.forceMkdir(new File(path));
			File file = new File(path, newFileName);
			try {
				multipartFile.transferTo(file);
			} catch (Exception e) {
				log.error(e.getMessage(), e);
			}
		}
		return newFileName;
	}

	public static List<String> uploadFile(String path, MultipartFile[] files) throws IOException {
		return uploadFile(path, files, null);
	}

	public static List<String> uploadFile(String path, MultipartFile[] files, String fileNamePrefix)
			throws IOException {
		return uploadFile(path, files, fileNamePrefix, Boolean.FALSE);
	}

	public static List<String> uploadFile(String path, MultipartFile[] files, String fileNamePrefix,
			boolean isAddOriginalFileNme) throws IOException {
		List<String> newFileNames = new ArrayList<>();
		for (MultipartFile multipartFile : files) {
			newFileNames.add(uploadFile(path, multipartFile, fileNamePrefix, isAddOriginalFileNme));
		}
		return newFileNames;
	}
	
	/**
	 * 上传文件并压缩
	 * @param path
	 * @param files
	 * @param fileNamePrefix
	 * @param isAddOriginalFileNme
	 * @return
	 * @throws IOException 
	 */
	public static List<String> uploadFileAndScale(String path, MultipartFile[] files, String fileNamePrefix,boolean isAddOriginalFileNme) throws IOException{
		List<String> newFileNames = new ArrayList<>();
		for (MultipartFile multipartFile : files) {
			newFileNames.add(uploadFileAndScale(path, multipartFile, fileNamePrefix, isAddOriginalFileNme));
		}
		return newFileNames;
	}
	
	public static String uploadFileAndScale(String path, MultipartFile multipartFile, String fileNamePrefix,boolean isAddOriginalFileNme) throws IOException {
		String newFileName = null;
		if (!multipartFile.isEmpty()) {
			String fileName = multipartFile.getOriginalFilename();
			String fileType = getExtensionNme(fileName);
			if (isAddOriginalFileNme) {
				newFileName = StringUtil.nullToEmpty(fileNamePrefix) + Contants.getGuid() + "-" + getFileNme(fileName)
						+ '.' + fileType;
			} else {
				newFileName = StringUtil.nullToEmpty(fileNamePrefix) + Contants.getGuid() + '.' + fileType;
			}
			FileUtils.forceMkdir(new File(path));
			File file = new File(path, newFileName);
			double scale=0d;
			double quality=0d;
			if(multipartFile.getSize()<1024*512){
				//小于等于512k的
				scale=0.9d;
				quality=1d;
			}else if(multipartFile.getSize()<1024*1024*2){
				//大于512 小于2M
				scale=0.6d;
				quality=0.8d;
			}else if(multipartFile.getSize()<1024*1024*7){
				//大于2M 小于7M
				scale=0.5d;
				quality=0.6d;
			}else{
				//大于7M
				scale=0.25d;
				quality=0.5d;
			}
			Thumbnails.of(multipartFile.getInputStream()).scale(scale).outputQuality(quality).toFile(file);
			/*File file = new File(path, newFileName);
			try {
				multipartFile.transferTo(file);
			} catch (Exception e) {
				log.error(e.getMessage(), e);
			}*/
		}
		return newFileName;
	}

	// 手机端图片上传获取路径
	public static String uploadFileMobile(String path, String bast64Data, String fileNamePrefix, String uploadFileType)
			throws IOException {
		return decodeBase64(bast64Data, path, fileNamePrefix, uploadFileType);
	}

	public static Map<String, String> uploadFiles(String path, MultipartFile[] files) throws IOException {
		Map<String, String> fileNamePairs = new HashMap<>();
		for (MultipartFile multipartFile : files) {
			fileNamePairs.put(uploadFile(path, multipartFile, null, Boolean.FALSE),
					multipartFile.getOriginalFilename());
		}
		return fileNamePairs;
	}

	public static void writeTxtFile(String path, String fileNme, String content) {
//		String fullPath = path + File.separator + fileNme;
//		try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fullPath, true)));) {
//			out.write(content + EbConstants.LINE_SEPARATOR);
//		} catch (Exception e) {
//			log.error(e.getMessage(), e);
//		}
	}

	public static byte[] writeZip(Map<String, byte[]> byteList) {
		try (ByteArrayOutputStream os = new ByteArrayOutputStream(); ZipOutputStream zos = new ZipOutputStream(os);) {
			for (Map.Entry<String, byte[]> entry : byteList.entrySet()) {
				ZipEntry ze = new ZipEntry(entry.getKey());
				zos.putNextEntry(ze);
				zos.write(entry.getValue());
				zos.flush();
				zos.closeEntry();
				os.flush();
			}
			return os.toByteArray();
		} catch (IOException e) {
			log.error(e.getMessage(), e);
		}
		return new byte[0];
	}

	public static void writeZipFile(OutputStream os, List<File> files) {
		if (CollectionUtils.isEmpty(files)) {
			return;
		}
		try (ZipOutputStream zos = new ZipOutputStream(os)) {
			for (File file : files) {
				ZipEntry ze = new ZipEntry(file.getName());
				zos.putNextEntry(ze);
				int len;
				byte[] buf = new byte[2 * 1024];
				try (FileInputStream in = new FileInputStream(file);) {
					while ((len = in.read(buf)) != -1) {
						zos.write(buf, 0, len);
					}
					zos.flush();
					zos.closeEntry();

				} catch (IOException e) {
					log.error(e.getMessage(), e);
				}
			}
		} catch (IOException e) {
			log.error(e.getMessage(), e);
		}
	}

	public static void writeZipFile(OutputStream os, Map<String, byte[]> byteList) {
		try (ZipOutputStream zos = new ZipOutputStream(os)) {
			for (Map.Entry<String, byte[]> entry : byteList.entrySet()) {
				ZipEntry ze = new ZipEntry(entry.getKey());
				zos.putNextEntry(ze);
				zos.write(entry.getValue());
				zos.flush();
				zos.closeEntry();
			}
		} catch (IOException e) {
			log.error(e.getMessage(), e);
		}
	}

	private DiaoerFileUtils() {
	}

}
